package com.itcoon.cloud.framework.security.config;
/**
 * @author Athric Yu
 * 2025/1/3
 **/

import com.itcoon.cloud.framework.security.config.properties.AuthEntrypointManageProperties;
import com.itcoon.cloud.framework.security.config.properties.JwtTokenProperties;
import com.itcoon.cloud.framework.security.filter.SwaggerApiKeyAuthFilter;
import com.itcoon.cloud.framework.security.filter.SwaggerBasicAuthFilter;
import com.itcoon.cloud.framework.utils.MapUtils;
import com.itcoon.cloud.framework.webmvc.config.properties.GlobalWebConfigProperties;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.MultiValueMapAdapter;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPattern;

import javax.annotation.security.PermitAll;
import java.util.*;

/**
 *
 *
 * @author Athric Yu
 * 2025/1/3
 **/
@AutoConfiguration
@Order(99)
@EnableConfigurationProperties({AuthEntrypointManageProperties.class, JwtTokenProperties.class})
public class DefaultSecurityAutoConfiguration {

    private static final Logger log = LoggerFactory.getLogger(DefaultSecurityAutoConfiguration.class);

    @Autowired
    private AuthEntrypointManageProperties manageProperties;

    @Autowired
    private LogoutSuccessHandler logoutSuccessHandler;

    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    @Autowired
    private LogoutHandler logoutHandler;

    @Autowired
    private List<HttpSecurityAdvice> httpSecurityAdvices;

    @Autowired(required = false)
    private SwaggerBasicAuthFilter swaggerBasicAuthFilter;

    @Autowired(required = false)
    private SwaggerApiKeyAuthFilter swaggerApiKeyAuthFilter;

    @Autowired
    private GlobalWebConfigProperties webConfigProperties;

    @Autowired
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    @Bean
    public WebSecurityCustomizer webSecurity(){
        return web -> web.ignoring()
                .antMatchers("/swagger-ui/**", "/v3/api-docs", "/v3/api-docs/**");
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        List<AuthEntrypointManageProperties.Path> paths = manageProperties.getPaths();
        if(CollectionUtils.isEmpty(paths)){
            GlobalWebConfigProperties.Api[] apis = webConfigProperties.getApis();
            paths = new ArrayList();
            for (GlobalWebConfigProperties.Api api : apis) {
                String basePath = api.generalBasePath();
                    paths.add(new AuthEntrypointManageProperties.Path(
                            basePath + "/**",
                            Collections.emptyList(),
                            Collections.emptyList(),
                            false,
                            basePath + "/auth/login",
                            basePath + "/auth/logout",
                            api.getCorsEnable()
                    ));
            }
        }

        MultiValueMap<HttpMethod, String> permitAllUrls = getPermitAllUrlsFromAnnotations(requestMappingHandlerMapping);
        for (AuthEntrypointManageProperties.Path path : paths) {
            ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
                    .mvcMatcher(path.getBasic())
                    .authorizeRequests();

            for(Map.Entry<HttpMethod, List<String>> entry : permitAllUrls.entrySet()){
                log.info("PermitAll(), httpMethod={}, URI = {}", entry.getKey().name(), Strings.join(entry.getValue(), ','));
                if (entry.getValue().stream().allMatch(s -> new AntPathMatcher().match(path.getBasic(), s))) {
                    registry.antMatchers(entry.getKey(), entry.getValue().toArray(new String[0])).permitAll();
                }
            }


            HttpSecurity httpSecurity = registry.antMatchers(path.getAnonymous().toArray(new String[0])).anonymous()
                     .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginProcessingUrl(path.getLogin()).failureHandler(authenticationFailureHandler).successHandler(authenticationSuccessHandler)
                    .and()
                    .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler)
                    .and()
                    .headers()
                    .cacheControl()
                    .and()
                    .frameOptions()
                    .disable()
                    .and()
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .logout().logoutUrl(path.getLogout()).addLogoutHandler(logoutHandler).logoutSuccessHandler(logoutSuccessHandler).clearAuthentication(true)
                    .and()
                    .httpBasic().disable()
                    .csrf().disable();

            if(swaggerApiKeyAuthFilter != null){
                 httpSecurity.addFilterBefore(swaggerApiKeyAuthFilter, UsernamePasswordAuthenticationFilter.class);
            }

            if(swaggerBasicAuthFilter != null){
                httpSecurity.addFilterBefore(swaggerBasicAuthFilter, UsernamePasswordAuthenticationFilter.class);
            }

            for (HttpSecurityAdvice httpSecurityAdvice : httpSecurityAdvices) {
                httpSecurityAdvice.advice(path, httpSecurity);
            }

            if (path.getCors()) {
                httpSecurity.cors().configurationSource(this.corsConfigurationSource());
            }

        }
        return http.build();
    }

    private MultiValueMap<HttpMethod, String> getPermitAllUrlsFromAnnotations(RequestMappingHandlerMapping requestMappingHandlerMapping) {
        Map<HttpMethod, List<String>> result = new HashMap<>();
        // 获得接口对应的 HandlerMethod 集合
        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
        // 获得有 @PermitAll 注解的接口
        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) {
            HandlerMethod handlerMethod = entry.getValue();
            if (!handlerMethod.hasMethodAnnotation(PermitAll.class)) {
                continue;
            }
            List<String> urls = new ArrayList<>();
            if (entry.getKey().getPatternsCondition() != null) {
                urls.addAll(entry.getKey().getPatternsCondition().getPatterns());
            }
            if (entry.getKey().getPathPatternsCondition() != null) {
                urls.addAll(MapUtils.mapAsList(entry.getKey().getPathPatternsCondition().getPatterns(), PathPattern::getPatternString));
            }
            if (urls.isEmpty()) {
                continue;
            }

            // 特殊：使用 @RequestMapping 注解，并且未写 method 属性，此时认为都需要免登录
            Set<RequestMethod> methods = entry.getKey().getMethodsCondition().getMethods();
            if (CollectionUtils.isEmpty(methods)) {
                result.put(HttpMethod.GET, urls);
                result.put(HttpMethod.POST, urls);
                result.put(HttpMethod.PUT, urls);
                result.put(HttpMethod.DELETE, urls);
                result.put(HttpMethod.HEAD, urls);
                result.put(HttpMethod.PATCH, urls);
            } else {
                for (RequestMethod method : methods) {
                    HttpMethod httpMethod = HttpMethod.valueOf(method.name());
                    List<String> values = result.computeIfAbsent(httpMethod, k -> new ArrayList<>());
                    values.addAll(urls);
                }
            }
        }
        return new MultiValueMapAdapter<>(result);
    }


    public CorsConfigurationSource corsConfigurationSource(){
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOriginPatterns(Collections.singletonList("*"));
        configuration.setAllowedMethods(Collections.singletonList("*"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}
